// ColorGlyphButton
// Distribué par http://www.developpez.com
// Auteur : Greybird ( http://greybird.developpez.com )
//
//---------------------------------------------------------------------------
#include <vcl.h>
#include <consts.hpp>
#pragma hdrstop

#include "MTAG_CtrlStd.h"
#include "CBitBtn.h"
#include "EIC.h"

#define MODULE_TAG MTAG_CBITBTN

//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
static inline void ValidCtrCheck(CBitBtn *)
{
  new CBitBtn(NULL);
}
//---------------------------------------------------------------------------

extern "C" const BitBtnOption BitBtnOptions[NB_KIND] =
{
  {NULL         ,0       ,NULL},
  {"BBOUI"      ,mrOk    ,"&Ok"},
  {"BBANNULER"  ,mrCancel,"&Annuler"},
  {"BBAIDE"     ,0       ,"&Aide"},
  {"BBOUI"      ,mrYes   ,"&Oui"},
  {"BBNON"      ,mrNo    ,"&Non"},
  {"BBFERMER"   ,0       ,"&Fermer"},
  {"BBANNULER"  ,mrAbort ,"&Abandonner"},
  {"BBREESSAYE" ,mrRetry ,"&Recommencer"},
  {"BBIGNORER"  ,mrIgnore,"&Ignorer"},
  {"BBTOUT"     ,mrAll   ,"&Tout"},
  {"BBADD"      ,0       ,"&Ajouter"},
  {"BBMODIFY"   ,0       ,"&Modifier"},
  {"BBDELETE"   ,0       ,"&Supprimer"},
  {"BBDUPLICATE",0       ,"&Dupliquer"},
  {"BBDETAIL"   ,0       ,"&Détail"},
  {"BBPRINT"    ,0       ,"&Imprimer"},
  {"BBPREVIEW"  ,0       ,"&Visualiser"},
  {"BBIMPORT"   ,0       ,"&Importer"},
  {"BBEXPORT"   ,0       ,"&Exporter"},
  {"BBNEXT"     ,0       ,"&Suivant"},
  {"BBPRIOR"    ,0       ,"&Précédent"},
  {"BBUP"       ,0       ,"&Précédent"},
  {"BBDOWN"     ,0       ,"&Suivant"},
  {"BBSAVE"     ,0       ,"&Enregistrer"},
  {"BBVISA"     ,0       ,"&Viser"},
  {"BBCACHER"   ,0       ,"&Cacher"},
  {"BBLOCK"     ,0       ,"&Vérrouiller"},
  {"BBUNLOCK"   ,0       ,"&Dévérrouiller"},
  {"BBRESTORE"  ,0       ,"&Restaurer"},
  {"BBPARCOURIR",0       ,"&Parcourir"},
  {"BBMAIL"     ,0       ,"e-Mail"},
  {"BBTOUTSELECT",0      ,"Tout &sélectionner"},
  {"BBINVERSERSELECT",0  ,"&Inverser la sélection"},
  {"BBTOUTDESELECT",0    ,"Tout &désélectionner"},
  {"BBTOUTSUPPRIMER",0   ,"&Tout supprimer"},
  {"BBLETTRER"  ,0       ,"&Lettrer"},
  {"BBDELETTRER",0       ,"&Délettrer"},
  {"BBPRELETTRER",0      ,"&Prélettrer"},
  {"BBSELECTIONNER",0    ,"&Sélectionner"},
  {"BBDESELECTIONNER",0  ,"&Désélectionner"},
  {"BBADD2"      ,0      ,"&Ajouter à la liste"},
  {"BBCALCULAUTO",0      ,"&Calcul automatique"},
  {"BBPDF"      ,0       ,"&Impression PDF"},
  {"BBREMPLACER",0       ,"&Remplacer"},
  {"BB3POINTS"  ,0       ,""},
  {"BBMOVEUP"   ,0       ,"Monter"},
  {"BBMOVEDOWN" ,0       ,"Descendre"},
  {"BBRECHERCHER",0      ,"Rechercher"},
  {"BBEFFACER"   ,0      ,"E&ffacer"},
  {"BBRAFRAICHIR",0      ,"Rafraîchir"},
};

//---------------------------------------------------------------------------
// Fonction qui permet de récupérer une couleur plus foncée que celle passée
// en paramètre, pour la gestion 3D du bouton
//---------------------------------------------------------------------------
TColor __fastcall GetColorLo(int _nColor, int _nPasParCouleur)
{
    TColor ColorRetour = (TColor)0x000000;
    if (_nColor == clBtnFace) {
        ColorRetour = clBtnShadow;

    } else {
        int R = (0x0000FF & _nColor);
        int G = (0x00FF00 & _nColor) >> 8 ;
        int B = (0xFF0000 & _nColor) >> 16;

        if((R - _nPasParCouleur) < 0x00)
             R = 0;
        else R -= _nPasParCouleur;

        if((G - _nPasParCouleur) < 0x00)
             G = 0;
        else G -= _nPasParCouleur;

        if((B - _nPasParCouleur) < 0x00)
             B = 0;
        else B -= _nPasParCouleur;

        ColorRetour = (TColor)(R | (G << 8) | (B << 16));
    }
    return(ColorRetour);
}           
//---------------------------------------------------------------------------
// Fonction qui permet de récupérer une couleur plus claire que celle passée
// en paramètre, pour la gestion 3D du bouton
//---------------------------------------------------------------------------
TColor __fastcall GetColorHi(int _nColor, int _nPasParCouleur)
{
    TColor ColorRetour = (TColor)0x000000;
    if (_nColor == clBtnFace) {
        ColorRetour = clBtnHighlight;

    } else {
        int R = (0x0000FF & _nColor);
        int G = (0x00FF00 & _nColor) >> 8 ;
        int B = (0xFF0000 & _nColor) >> 16;

        if((R + _nPasParCouleur) > 0xFF)
             R = 0xFF;
        else R += _nPasParCouleur;

        if((G + _nPasParCouleur) > 0xFF)
             G = 0xFF;
        else G += _nPasParCouleur;

        if((B + _nPasParCouleur) > 0xFF)
             B = 0xFF;
        else B += _nPasParCouleur;

        ColorRetour = (TColor)(R | (G << 8) | (B << 16));
    }
    return(ColorRetour);
}

/*******************************************************************************

Fonction :

Paramètres d'entrée :

Paramètres modifiés :

Valeur retournée :
*******************************************************************************/
void __fastcall DoDrawButtonFace(TCanvas* Canvas,
                                 const TRect &ClientRect,
                                 bool bFlatMode,
                                 bool bTransparent,
                                 TButtonState State,
                                 bool bMouseInControl,
                                 TColor Color,
                                 bool  bDessinParDefaut,
                                 const TOwnerDrawState& state,
                                 bool bDown)
{
  TRect RClient = ClientRect;

  if (bFlatMode)
     {
     if (State == bsDown || State == bsExclusive || bMouseInControl)
        DrawEdge(Canvas->Handle,
                 &RClient,
                 (State == bsDown || State == bsExclusive) ? BDR_SUNKENOUTER : BDR_RAISEDINNER,
                 (bTransparent ? 0 : BF_MIDDLE) | BF_RECT);
     else if (!bTransparent)
        {
        Canvas->Brush->Color = Color;
        Canvas->FillRect(RClient);
        }
     InflateRect(&RClient, -1, -1);
     }
  else
     {
     //SI le bouton est le bouton par défaut ou a le focus
     //=> On grossi l'encadrement
     if (bDessinParDefaut || state.Contains(odFocused))
       {
       Canvas->Brush->Color = Color;
       Canvas->Pen->Color = clWindowFrame;
       Canvas->Rectangle(RClient.Left, RClient.Top,RClient.Right, RClient.Bottom);
       InflateRect(reinterpret_cast<PRECT>(&RClient),-1, -1);
       }
     else
       Canvas->Brush->Color = Color;

     // si le bouton est enfoncé
     if (bDown)
     {
       Canvas->Pen->Color = GetColorHi(Color);
       Canvas->Rectangle(RClient.Left, RClient.Top,RClient.Right, RClient.Bottom);
       DrawEdge(Canvas->Handle,
                &RClient,
                BDR_SUNKENOUTER,
                BF_RECT);
     }
     else if (state.Contains(odSelected))
       {
         Canvas->Pen->Color = GetColorLo(Color);
         Canvas->Rectangle(RClient.Left, RClient.Top,RClient.Right, RClient.Bottom);
       }
     else // non enfoncé
       {
       Canvas->FillRect(RClient);
       Frame3D(Canvas, RClient, GetColorHi(Color), clWindowFrame, 1);
       POINT P[] = {{1, RClient.Bottom - 1},{RClient.Right - 1,RClient.Bottom - 1},{RClient.Right - 1,RClient.Top - 1}};
       Canvas->Pen->Color = GetColorLo(Color);
       Canvas->Polyline(reinterpret_cast<TPoint*>(P), 2);
       }
     }
}

/*******************************************************************************

Fonction :

Paramètres d'entrée :

Paramètres modifiés :

Valeur retournée :
*******************************************************************************/
TPoint __fastcall GetPosGlyph(TCanvas* Canvas,
                              const TRect &ClientRect,
                              const AnsiString &aCaption,
                              Graphics::TBitmap* LeGlyph,
                              int Margin,
                              int Spacing,
                              const TOwnerDrawState& state)
{
//La longueur de la chaine est prise en compte en négligeant les '&' qui seront transformés
//en raccourcis clavier. => Buggé lorsque la chaine contient "&&" car cela représente un caractère et non un raccourci.
const int text_width = Canvas->TextWidth(StringReplace(aCaption, "&", EmptyStr, TReplaceFlags() << rfReplaceAll))/* + 2*/;
TPoint PGlyph;

// détermine la position où dessiner le Glyph
if (Margin == -1)
   {
   if (Spacing != -1 && text_width)
      PGlyph.x = (ClientRect.Right - ClientRect.Left - text_width - Spacing - LeGlyph->Width) / 2.0;
   else
      PGlyph.x = (ClientRect.Right - ClientRect.Left - (text_width + LeGlyph->Width))/ (text_width == 0 ? 2.0 : 3.0);
   }
else
   PGlyph.x = 1 + Margin;

PGlyph.y = (ClientRect.Bottom - ClientRect.Top - LeGlyph->Height) / 2.0;

// décale le Glyph si le bouton est enfoncé
if (state.Contains(odSelected))
  {
  PGlyph.x += 1;
  PGlyph.y += 1;
  }

return PGlyph;
}
/*******************************************************************************

Fonction :

Paramètres d'entrée :

Paramètres modifiés :

Valeur retournée :
*******************************************************************************/
void __fastcall DoDrawButtonText(TCanvas* Canvas,
                                 const TRect &ClientRect,
                                 TColor Color,
                                 TColor FontColor,
                                 const AnsiString &aCaption,
                                 CBitBtnKind Kind,
                                 Graphics::TBitmap *Glyph,
                                 int Margin,
                                 int Spacing,
                                 const TOwnerDrawState& state)
{
  if (aCaption.Length() == 0)
    return;

  RECT RText = {0, 0, ClientRect.Right - ClientRect.Left, ClientRect.Bottom - ClientRect.Top};
  bool bAvecGlyph = false;
  SetBkMode(Canvas->Handle, TRANSPARENT);

  if (Kind == Cbitbtn::bkCustom && !Glyph->Empty)
    {
    TPoint PGlyph;
    PGlyph = GetPosGlyph(Canvas, ClientRect, aCaption, Glyph, Margin, Spacing, state);
    RText.left = PGlyph.x + Glyph->Width;
    bAvecGlyph = true;
    }
  else if (Kind != Cbitbtn::bkCustom)
    {
    TPoint PGlyph;
    Graphics::TBitmap *LeGlyph = new Graphics::TBitmap;
//    lstGlyphs->GetBitmap(GlyphIndex, LeGlyph);
    LeGlyph->LoadFromResourceName((int)HInstance, BitBtnOptions[Kind].Name);

    PGlyph = GetPosGlyph(Canvas, ClientRect, aCaption, LeGlyph, Margin, Spacing, state);
    RText.left = PGlyph.x + LeGlyph->Width;

    bAvecGlyph = true;

    delete LeGlyph;     LeGlyph = NULL;
    }
  else
     //On centre => La longueur de la chaine est prise en compte en négligeant les '&' qui seront transformés
     //en raccourcis clavier. => Buggé lorsque la chaine contient "&&" car cela représente un caractère et non un raccourci.
     RText.left = (ClientRect.Right - ClientRect.Left - Canvas->TextWidth(StringReplace(aCaption, "&", EmptyStr, TReplaceFlags() << rfReplaceAll)))/2;

  if (bAvecGlyph && RText.left != 0)
     RText.left += Spacing;

  // si le bouton est enfoncé
  if (state.Contains(odSelected))
  {
    // décale le texte
    OffsetRect(&RText, 1, 1);
  }

  // si le bouton est désactivé
  if (state.Contains(odDisabled))
    {
    //Cas où le bouton a des couleurs standards
    // => On dessine l'ombre du texte
    if (Color == clBtnFace && FontColor == clWindowText)
       {
       OffsetRect(&RText, 1, 1);
       Canvas->Font->Color = GetColorHi(Color, 0x40);
       DrawText(Canvas->Handle, aCaption.c_str(), -1, &RText, DT_VCENTER | DT_SINGLELINE);
       OffsetRect(&RText, -1, -1);
       }

    //On atténue la couleur du texte
    // => elle doit se rapprocher de la couleur du fond
    Canvas->Font->Color = (TColor)RGB(1/3. * GetRValue(ColorToRGB(FontColor)) + 2/3. * GetRValue(ColorToRGB(Color)),
                                      1/3. * GetGValue(ColorToRGB(FontColor)) + 2/3. * GetGValue(ColorToRGB(Color)),
                                      1/3. * GetRValue(ColorToRGB(FontColor)) + 2/3. * GetRValue(ColorToRGB(Color)));
    }
  else
    SetTextColor(Canvas->Handle, ColorToRGB(FontColor));

  // Ecriture du texte
  DrawText(Canvas->Handle, aCaption.c_str(), -1, &RText, DT_VCENTER | DT_SINGLELINE);

  // si le bouton a le focus
  if (state.Contains(odFocused))
  {
    // dessine le rectangle de sélection
    TRect RFocus = ClientRect;
    InflateRect(reinterpret_cast<PRECT>(&RFocus),-4, -4);
    Canvas->DrawFocusRect(RFocus);
  }
}
//---------------------------------------------------------------------------
void __fastcall DoDrawButtonGlyph(TCanvas* Canvas,
                                  const TRect &ClientRect,
                                  TColor Color,
                                  const AnsiString &aCaption,
                                  CBitBtnKind Kind,
                                  Graphics::TBitmap *Glyph,
                                  int Margin,
                                  int Spacing,
                                  const TOwnerDrawState& state)
{
  Graphics::TBitmap* LeGlyph = NULL;
  TPoint PGlyph;
  TRect RectDest;

  if (!Glyph->Empty)
     {
     LeGlyph = new Graphics::TBitmap;
     LeGlyph->Assign(Glyph);

     PGlyph = GetPosGlyph(Canvas, ClientRect, aCaption, LeGlyph, Margin, Spacing, state);
     RectDest.left   = PGlyph.x;
     RectDest.top    = PGlyph.y;
     RectDest.right  = min(PGlyph.x + LeGlyph->Width, (long)ClientRect.Right - ClientRect.Left - GetSystemMetrics(SM_CXEDGE));
     RectDest.bottom = min(PGlyph.y + LeGlyph->Height, (long)ClientRect.Bottom - ClientRect.Top - GetSystemMetrics(SM_CYEDGE));

     // Dessine le Glyph inactif
     if (state.Contains(odDisabled))
       {
       Graphics::TBitmap *MaskBitmap = NULL;
       MaskBitmap = new Graphics::TBitmap;
       MaskBitmap->Monochrome = true;
       MaskBitmap->Width = LeGlyph->Width;
       MaskBitmap->Height = LeGlyph->Height;
       // Génère un mask noir qui exclue le fond et les pixels blancs
       SetBkColor(LeGlyph->Canvas->Handle, LeGlyph->TransparentColor);
       BitBlt(MaskBitmap->Canvas->Handle, 0, 0, LeGlyph->Width, LeGlyph->Height, LeGlyph->Canvas->Handle, 0, 0, SRCCOPY);
       SetBkColor(LeGlyph->Canvas->Handle, RGB(0xff, 0xff, 0xff));
       BitBlt(MaskBitmap->Canvas->Handle, 0, 0, LeGlyph->Width, LeGlyph->Height, LeGlyph->Canvas->Handle, 0, 0, NOTSRCERASE);

       // draw the new mask 'etched' to hdc
       SetBkColor(Canvas->Handle, RGB(255, 255, 255));
       SelectObject(Canvas->Handle, GetSysColorBrush(COLOR_3DHILIGHT));
       // E20746 op code is (Dst ^ (Src & (Pat ^ Dst))) => Dessine la zone claire, puis la zone sombre
       BitBlt(Canvas->Handle, PGlyph.x + 1, PGlyph.y + 1, LeGlyph->Width, LeGlyph->Height, MaskBitmap->Canvas->Handle, 0, 0, 0xE20746);
       SelectObject(Canvas->Handle, GetSysColorBrush(COLOR_3DSHADOW));
       BitBlt(Canvas->Handle, PGlyph.x, PGlyph.y, LeGlyph->Width, LeGlyph->Height, MaskBitmap->Canvas->Handle, 0, 0, 0xE20746);

       delete MaskBitmap;
       }
     else
       {
       Canvas->Brush->Color = Color;
       Canvas->BrushCopy(RectDest,
                         LeGlyph,
                         LeGlyph->Canvas->ClipRect,
                         LeGlyph->TransparentColor);
       }
     }

delete LeGlyph;
}

void __fastcall CBitBtn::InitListeGlyphs()
{
/*  Graphics::TBitmap *pBitmap = NULL;
  if (!NbCBitBtn_++)
    {
      ASSERT(!Glyphs_);

      Glyphs_ = new TImageList(NULL);
      Glyphs_->Height         = 16;
      Glyphs_->Width          = 16;
      Glyphs_->DrawingStyle   = dsTransparent;
      Glyphs_->Masked         = false;

      pBitmap = new Graphics::TBitmap();
      for (int i=0; i<NB_KIND; i++)
        if (BitBtnOptions[i].Name)
          {
            pBitmap->LoadFromResourceName((int)HInstance,BitBtnOptions[i].Name);
            Glyphs_->Add(pBitmap, NULL);
          }
      delete pBitmap;
    }

  ASSERT(Glyphs_);*/
}
void __fastcall CBitBtn::DeInitListeGlyphs()
{/*
  ASSERT(Glyphs_);

  if (!--NbCBitBtn_)
    {
      delete Glyphs_;
      Glyphs_ = NULL;
    } */
}

namespace Cbitbtn
{
  void __fastcall PACKAGE Register()
  {
    TComponentClass classes[1] = {__classid(CBitBtn)};
    RegisterComponents("EIC Ctrlstd", classes, 0);
  }
}
//---------------------------------------------------------------------------
__fastcall CBitBtn::CBitBtn(TComponent* Owner) : TButton(Owner),
    Canvas_(new TCanvas()),
    draw_as_default_(false)
{
  m_Glyph = new Graphics::TBitmap;;
  m_Margin = -1;
  m_Spacing = 4;
  FKind = Cbitbtn::bkCustom;
  InitListeGlyphs();
}

__fastcall CBitBtn::~CBitBtn()
{
  DeInitListeGlyphs();
  delete m_Glyph;     m_Glyph = NULL;
}
//---------------------------------------------------------------------------
void __fastcall CBitBtn::SetButtonStyle(bool ADefault)
{
  if (draw_as_default_ != ADefault)
  {
    draw_as_default_ = ADefault;
    InvalidateRect(Handle, NULL, FALSE);
  }
}
//---------------------------------------------------------------------------
void __fastcall CBitBtn::CNDrawItem(TMessage& Msg)
{
  // récupère un pointeur sur le DRAWITEMSTRUCT
  const DRAWITEMSTRUCT* pDrawItem = reinterpret_cast<DRAWITEMSTRUCT*>(Msg.LParam);

  // sauve l'état actuel du DC
  SaveDC(pDrawItem->hDC);
  // relie le Canvas au DC cible
  Canvas_->Handle = pDrawItem->hDC;

  //Prendre en compte la fonte du composant pour dessiner
  Canvas_->Font->Assign(inherited::Font);
  try
  {
    // extrait les flags d'état
    TOwnerDrawState state;

    // si le bouton a le focus
    if (pDrawItem->itemState & ODS_FOCUS)
    {
      state = state << odFocused;
    }

    // si le bouton est enfoncé
    if (pDrawItem->itemState & ODS_SELECTED)
    {
      state = state << odSelected;
    }

    // si le bouton est désactivé
    if (pDrawItem->itemState & ODS_DISABLED || !Enabled)
    {
      state = state << odDisabled;
    }

    // dessine le bouton
    DoDrawButtonFace(Canvas_.get(),
                     ClientRect,
                     false,
                     false,
                     state.Contains(odSelected) ? bsDown : bsUp,
                     false,
                     Brush->Color,
                     draw_as_default_,
                     state,
                     false);

    // dessine le Glyph
    DoDrawButtonGlyph(Canvas_.get(),
                      ClientRect,
                      Color,
                      Caption,
                      FKind,
                      m_Glyph,
                      m_Margin,
                      m_Spacing,
                      state);

    // écrit le texte
    DoDrawButtonText(Canvas_.get(),
                     ClientRect,
                     Color,
                     Font->Color,
                     Caption,
                     FKind,
                     m_Glyph,
                     m_Margin,
                     m_Spacing,
                     state);
   }
  catch (...)
  {
    // nettoyage
    Canvas_->Handle = NULL;
    RestoreDC(pDrawItem->hDC, -1);
  }
  // nettoyage
  Canvas_->Handle = NULL;
  RestoreDC(pDrawItem->hDC, -1);

  // message traité
  Msg.Result = TRUE;
}
//---------------------------------------------------------------------------
void __fastcall CBitBtn::WMLButtonDblClk(TMessage& Msg)
{
  SNDMSG(Handle, WM_LBUTTONDOWN,Msg.WParam, Msg.LParam);
}
//---------------------------------------------------------------------------
void __fastcall CBitBtn::CMFontChanged(TMessage& Msg)
{
  TButton::Dispatch(&Msg);
  InvalidateRect(Handle, NULL, TRUE);
}
//---------------------------------------------------------------------------
void __fastcall CBitBtn::CMEnabledChanged(TMessage& Msg)
{
  TButton::Dispatch(&Msg);
  InvalidateRect(Handle, NULL, TRUE);
}
//---------------------------------------------------------------------------
void __fastcall CBitBtn::WMEraseBkgnd(TMessage& Msg)
{
  Msg.Result = 1;
}
//---------------------------------------------------------------------------
void __fastcall CBitBtn::SetKind(CBitBtnKind Value)
{
  if (Value != FKind )
  {
    if (Value != Cbitbtn::bkCustom)
    {

      m_Glyph->LoadFromResourceName((int)HInstance, BitBtnOptions[Value].Name);
      if (!ComponentState.Contains(csLoading))
      {
        if (Value == Cbitbtn::bkCancel|| Value == Cbitbtn::bkNo)
          Cancel = true;
        if (BitBtnOptions[Value].Caption && (Caption.IsEmpty() || (Caption == Name)))
          Caption = BitBtnOptions[Value].Caption;
        if (ModalResult == mrNone)
          ModalResult = BitBtnOptions[Value].ModalResult;
      }
    }
    FKind = Value;
    Invalidate();
  }
}

/*******************************************************************************

Fonction :

Paramètres d'entrée :

Paramètres modifiés :

Valeur retournée :
*******************************************************************************/
void __fastcall CBitBtn::SetGlyph(Graphics::TBitmap* Value)
{
m_Glyph->Assign(Value);
Invalidate();
}

bool __fastcall CBitBtn::IsCustom()
{
return FKind == bkCustom;
}

/*******************************************************************************

Fonction :

Paramètres d'entrée :

Paramètres modifiés :

Valeur retournée :
*******************************************************************************/
void __fastcall CBitBtn::SetMargin(int iMargin)
{
if (iMargin != m_Margin && iMargin >= -1)
   {
   m_Margin = iMargin;
   Invalidate();
   }
}

/*******************************************************************************

Fonction :

Paramètres d'entrée :

Paramètres modifiés :

Valeur retournée :
*******************************************************************************/
void __fastcall CBitBtn::SetSpacing(int iSpacing)
{
if (iSpacing != m_Spacing && iSpacing >= -1)
   {
   m_Spacing = iSpacing;
   Invalidate();
   }
}


